<?php

namespace app\api\services;

use app\api\consDir\SandConst;
use app\common\libs\Singleton;
use app\common\models\Order\OrderError;
use Exception;
use think\facade\App;
use think\facade\Log;

class SandService
{
    use Singleton;

    protected $publicKeyPath;
    protected $privateKeyPath;
    protected $notifyUrl = '';
    protected $accessMid = '';
    protected $consumeMid = '';
    protected $privateKeyPwd = '';

    protected $domain = SandConst::DOMAIN;
    protected $prefix = SandConst::PREFIX;
    protected $frontUrl = SandConst::FRONT_URL;

    public function __construct()
    {
        if (env('test.is_test') == 1) {
            $this->domain = SandConst::TEST_DOMAIN;
            $this->frontUrl = SandConst::TEST_FRONT_URL;
        }
        $this->publicKeyPath = App::getRootPath() . 'cert/' . env('SAND.publicKeyPath');
        $this->privateKeyPath = App::getRootPath() . 'cert/' . env('SAND.privateKeyPath');
        $this->notifyUrl = env('app.apiDomain') . '/api/callback/sandCallback';
        $this->accessMid = env('SAND.accessMid');
        $this->consumeMid = env('SAND.consumeMid');
        $this->privateKeyPwd = env('SAND.privateKeyPwd');
    }

    public function accountOneKey($nickName, $name, $idCard, $phone, $userId): array
    {
        $order = getNo('sand');
        $params = [
            "outOrderNo" => $order,
            "bizUserNo" => $this->prefix . $userId,
            "notifyUrl" => $this->notifyUrl,
            "userInfo" =>
                [
                    "nickName" => $nickName,
                    "name" => $name,
                    "idcardType" => "01",
                    "idcardNumber" => $idCard,
                    "phone" => $phone,
                    "lockFlag" => "01", //01 不能修改开户实名信息， 默认00 可以修改
                ]
        ];
        return $this->sandSend($this->domain . SandConst::ACCOUNT_ONE_KEY, $params);
    }

    public function accountBalanceQuery($userId): array
    {
        $params = [
            "bizUserNo" => $this->prefix . $userId,
            "sdAccType" => "ewallet"  //"ewallet：余额户，ebyf9：云账户合作宝易付户  默认查询所有账户
        ];
        return $this->sandSend($this->domain . SandConst::ACCOUNT_BALANCE_QUERY, $params);
    }

    public function accountMemberQuery($userId): array
    {
        $params = [
            "bizUserNo" => $this->prefix . $userId,
        ];
        return $this->sandSend($this->domain . SandConst::ACCOUNT_MEMBER_QUERY, $params);
    }

    public function accountMemberStatus($userId, $operationType): array
    {
        $params = [
            "outOrderNo" => getNo('sand'),
            "bizUserNo" => $this->prefix . $userId,
            "operationType" => $operationType,
        ];
        return $this->sandSend($this->domain . SandConst::ACCOUNT_MEMBER_STATUS_MANAGE, $params);
    }

    /**
     * 会员绑卡查询
     * @param $userId
     * @return array
     */
    public function accountBindCardQuery($userId): array
    {
        $params = [
            "bizUserNo" => $userId,
        ];
        return $this->sandSend($this->domain . SandConst::ACCOUNT_BIND_CARD_QUERY, $params);
    }

    /**
     * 提现
     * @param $userId
     * @param $amount
     * @param $orderNo
     * @param $cardNo
     * @return array
     */
    public function withdraw($userId, $amount, $orderNo, $cardNo): array
    {
        $params = [
            "outOrderNo" => $orderNo,
            "amount" => $amount,
            //"bizUserFeeAmt"     => "", //用户服务费
            "bizUserNo" => $this->prefix . $userId,
            "sdaccType" => "ewallet", //"ewallet：余额户，ebyf9：云账户合作宝易付户  默认查询所有账户
            "relatedCardNo" => $cardNo,
            "notifyUrl" => $this->notifyUrl,
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_WITHDRAW, $params);
    }

    /**
     * 账户变动明细
     * @param $userId
     * @param $beginDate
     * @param $endDate
     * @return array
     */
    public function detailQuery($userId, $beginDate, $endDate, $page, $pageSize): array
    {
        $params = [
            "bizUserNo" => $this->prefix . $userId,
            "sdaccType" => "ewallet",   //"ewallet：余额户，ebyf9：云账户合作宝易付户  默认查询所有账户
            //"beginDate" => "20240521",
            //"endDate" => "20240527",
            "beginDate" => $beginDate,
            "endDate" => $endDate,
            //"ioFlag"        => "00", //变动类型  "00：全部（默认）01: 出金 02：入金
            "pageNo" => $page,
            "pageSize" => $pageSize
        ];
        return $this->sandSend($this->domain . SandConst::DETAIL_QUERY, $params);
    }

    /**
     * 付款
     * @param $bankNo
     * @param $name
     * @param $amount
     * @return array
     */
    public function paymentOrderCreate($bankNo, $name, $amount): array
    {
        $params = [
            "mid" => $this->consumeMid,
            "outOrderNo" => "test" . rand(10000000, 99999999),
            "amount" => $amount,
            "currency" => "CNY",
            "payerInfo" => [
                "sdaccSubId" => "payment", //付款专户
            ],
            "payeeInfo" => [
                "accNo" => $bankNo,  //银行账户
                "accName" => $name,
                "accType" => "cup"//对私
            ]
        ];
        return $this->sandSend($this->domain . SandConst::PAYMENT_ORDER_CREATE, $params, 1);
    }

    /**
     * 付款订单查询
     * @param $orderNo
     * @return array
     */
    public function paymentOrderQuery($orderNo): array
    {
        $params = [
            "outOrderNo" => $orderNo,
            "mid" => $this->consumeMid,
            "outReqDate" => date('Ymd')   //付款下单时间
        ];
        return $this->sandSend($this->domain . SandConst::PAYMENT_ORDER_CREATE, $params, 1);
    }

    /**
     * 转账
     * @param $amount
     * @param $payerBizUserNo
     * @param $payeeBizUserNo
     * @param $bizUserFeeAmt
     * @param $orderNo
     * @return array
     */
    public function transfer($amount, $payerBizUserNo, $payeeBizUserNo, $bizUserFeeAmt, $orderNo, $transMode = "rt",$remark = ""): array
    {
        $params = [
            "outOrderNo" => $orderNo,
            "amount" => $amount,
            //"timeOut"     => "20220726122031", //支付超时时间
            //"bizUserFeeAmt" => $bizUserFeeAmt, //用户服务费
            "transMode" => $transMode, //confirm：转账确认模式 rt：实时转账模式 默认实时"
            //"operationTimeOut"     => date('YmdHis', strtotime('+1 minute')), //确认超时时间

            //付款方
            "payerInfo" => [
                "payerBizUserNo" => $payerBizUserNo,
//                "payerBizUserNo" => "6888802027142",
                "sdaccType" => "ewallet", //默认仅支持余额

            ],

            //收款方
            "payeeInfo" => [
                "payeeBizUserNo" => $payeeBizUserNo,
                //"payeeName"     => "收款方名称",
                //"busiType"     => "业务模式",
                //"payeeMid"     => "收款方用户归属的商户",
            ],
            "notifyUrl" => $this->notifyUrl,
            "frontUrl" => $this->frontUrl,
            "remark" => $remark
        ];
        if ($bizUserFeeAmt > 0) {
            $params['bizUserFeeAmt'] = $bizUserFeeAmt;
        }
        $data = $this->sandSend($this->domain . SandConst::TRANS_TRANSFER, $params);
        if ($data['resultStatus'] == 'success') {
            return ['code' => 1, 'data' => $data];
        } else {
            return ['code' => 0, 'data' => $data['errorDesc']];
        }
    }

    /**
     * @param $outOrderNo
     * @param $oriOutOrderNo
     * @param $amount
     * @param $operationType
     * @param $userId
     * @return array
     */
    public function transferConfirm($outOrderNo, $oriOutOrderNo, $amount, $operationType, $userId): array
    {
        $params = [
            "outOperationOrderNo" => $outOrderNo, //商户操作订单号
            "oriOutOrderNo" => $oriOutOrderNo,   //原转账订单号
            "oriAmount" => $amount,
            "operationType" => $operationType, //操作类型 confirm,cancel
            "payeeBizUserNo" => $userId, //原转账订单指定的收款方
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_TRANSFER_CONFIRM, $params);
    }

    /**
     * @param $outOrderNo
     * @param $money
     * @param $userId
     * @return array
     */
    public function freeze($outOrderNo,$money,$userId): array
    {
        $params = [
            "outOrderNo" => $outOrderNo, //商户操作订单号
            "sdaccType" => 'ewallet',   //ewallet：余额户，ebyf9：云账户合作宝易付户
            "bizUserNo" => SandConst::PREFIX . $userId,  //杉德系统中该商户下用户唯一编号
            "amount" => $money, //冻结金额
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_FUND_FREEZE, $params);
    }

    /**
     * @param $outOperationOrderNo
     * @param $oriOutOrderNo
     * @param $money
     * @return array
     */
    public function unfreeze($outOperationOrderNo,$oriOutOrderNo,$money): array
    {
        $params = [
            "outOperationOrderNo" => $outOperationOrderNo, //商户操作订单号
            "oriOutOrderNo" => $oriOutOrderNo, //原冻结订单号
            "oriAmount" => $money,   //原冻结金额
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_FUND_UNFREEZE, $params);
    }

    /**
     * 消费退款
     * @param $outOrderNo
     * @param $oriOutOrderNo
     * @param $amount
     * @return array
     */
    public function orderRefund($outOrderNo, $oriOutOrderNo, $amount): array
    {
        $params = [
            "mid" => $this->consumeMid,
            'marketProduct' => 'CSDB',
            "outReqTime" => date('YmdHis'),
            "outOrderNo" => $outOrderNo,
            "oriOutOrderNo" => $oriOutOrderNo,
            "amount" => $amount,
            "notifyUrl" => $this->notifyUrl,
            'refundReason' => '取消订单退回'
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_ORDER_REFUND, $params, 1);
    }

    /**
     * 消费 转让订单支付
     * @param $bizUserNo
     * @param $amount
     * @param $orderNo
     * @return array
     */
    public function orderCreate($bizUserNo, $amount, $orderNo): array
    {
        $params = [
            "mid" => $this->consumeMid,
            "outOrderNo" => $orderNo,
            "description" => "收款订单摘要",
            "amount" => $amount,
            "payerInfo" => [
                "plMid" => $this->accessMid,  //必填
                "bizUserNo" => $this->prefix . $bizUserNo,
                "frontUrl" => $this->frontUrl,
            ],
            "notifyUrl" => $this->notifyUrl,
            "marketProduct" => "CSDB",
            //QZF：标准线上收款
            //CSDB：企业杉德宝
            "outReqTime" => date('YmdHis'),
            "goodsClass" => "12",
            "payType" => "SANDEPAY",
            "payMode" => "H5"
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_ORDER_CREATE, $params, 1);
    }

    /**
     * 统一下单接口（主扫）
     * @param $amount
     * @param $orderNo
     * @return array
     */
    public function orderCreateScan($amount, $orderNo): array
    {
        $params = [
            "mid" => $this->consumeMid,
            "outOrderNo" => $orderNo,
            "description" => "收款订单摘要",
            "amount" => $amount,
            "payerInfo" => [
                "authCode" => ""
            ],
            "notifyUrl" => $this->notifyUrl,
            "marketProduct" => "CSDB",
            //QZF：标准线上收款
            //CSDB：企业杉德宝
            "outReqTime" => date('YmdHis'),
            "goodsClass" => "12",
            "payType" => "CUPPAY",
//            "payMode" =>"SCAN",//反扫
            "payMode" => "QR",//正扫
            "timeOut" => date('YmdHis', strtotime('+5 minute')),
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_ORDER_CREATE_SCAN, $params, 1);
    }

    /**
     * 消费订单查询
     * @param $outOrderNo
     * @return array
     */
    public function orderQuery($outOrderNo): array
    {
        $params = [
            "mid" => $this->consumeMid,
            "outOrderNo" => $outOrderNo,
            "outReqTime" => date('YmdHis'),
        ];
        return $this->sandSend($this->domain . SandConst::TRANS_ORDER_QUERY, $params, 1);
    }

    public function walletOrderQuery($outOrderNo): array
    {
        $params = [
            "outOrderNo" => $outOrderNo,
        ];
        return $this->sandSend($this->domain . SandConst::WALLET_ORDER_QUERY, $params);
    }

    private function sandSend($url, $params, $isConsume = 0): array
    {
        //dump(json_encode($params, 256));
        $mid = $this->accessMid;
        if ($isConsume == 1) {
            $mid = $this->consumeMid;
        }
        try {
            $publicKeyPath = $this->publicKeyPath;
            $privateKeyPath = $this->privateKeyPath;
            $privateKeyPwd = $this->privateKeyPwd;
            $publicKey = $this->loadX509Cert($publicKeyPath);
            $priKey = $this->loadPk12Cert($privateKeyPath, $privateKeyPwd);
            //通用公共报文
            $publicData = [
                "accessMid" => $mid,
                "timestamp" => date('Y-m-d H:i:s'),
                "version" => "4.0.0",
                "signType" => "RSA",
                "sign" => "",
                "encryptType" => "AES",   //AES加密
                "encryptKey" => "",
                "bizData" => ""
            ];

            // 商户签名流程
            // 1. 生成一个16位的随机字符串aseKey，该字符串仅包含大小写字母及数字。
            // 2. 将随机字符串转为byte数组aesKeyBytes，编码格式为UTF_8。
            // 3. 将请求报文中的bizData域转为byte数组，编码格式为UTF_8，并用aesKeyBytes用AES算法对其加密，并对结果进行base64转码，得到加密报文体encryptValueBytes。
            // 4. 将随机字符串byte数组aesKeyBytes使用杉德公钥进行RSA算法加密，并将结果进行base64转码即得到得到sandEncryptKey。
            // 5. 将加密报文体使用商户私钥进行RSA算法签名，得到sign。

            if ($publicData['encryptType'] == 'AES') {
                //step1 生成随机数
                $AESKey = $this->aes_generate(16);
                // step2、3: 使用AESKey加密报文 通过aesKeyBytes对Json进行AES加密生成data
                $publicData['bizData'] = $this->AESEncrypt($params, $AESKey);   //  AES 模式
                // step4: 把aesKeyBytes通过杉德公钥加密生成encryptKey，encryptType为"AES"
                $publicData['encryptKey'] = $this->RSAEncryptByPub($AESKey, $publicKey); //  AES 模式
            }

            // step5: 将加密后的data，通过商户私钥进行签名生成sign，signType为"SHA256WithRSA"
            $publicData['sign'] = $this->sign($publicData['bizData'], $priKey);

            // step6: post请求
            $result = $this->http_post_json($url, $publicData);
            $arr = json_decode($result, true);
            //dump($result);
            if ($arr['respCode'] == 'fail') {
                //echo $arr['respDesc'];
                return [];
            }
            //请求错误,没返回成功,打印错误
            $decryptPlainText = [];
            $verify = 0;
            if ($publicData['encryptType'] == 'AES') {
                // step6: 使用公钥验签报文$decryptPlainText
                $verify = $this->verify($arr['bizData'], $arr['sign'], $publicKey);
                // step7: 使用私钥解密AESKey
                $decryptAESKey = $this->RSADecryptByPri($arr['encryptKey'], $priKey);
                // step8: 使用解密后的AESKey解密报文
                $decryptPlainText = json_decode($this->AESDecrypt($arr['bizData'], $decryptAESKey), true);
            }

            /*return [
                'verify' => $verify == 1 ? '验签成功!' : '验签失败!',
                'params' => json_encode($params, JSON_UNESCAPED_UNICODE),
                'publicData' => json_encode($publicData),
                'response' => $decryptPlainText,
            ];*/
            //dump($decryptPlainText);
            return $decryptPlainText;
        } catch (\Exception $e) {
            //echo $e->getMessage();
            return [];
        }
    }

    /**
     * 支付回调
     * @throws Exception
     */
    public function callback(): bool
    {
        $data = input();
        $publicKey = $this->loadX509Cert($this->publicKeyPath);
        $ret = $this->verify($data['bizData'], $data['sign'], $publicKey);
        if ($ret != 1) {
            return false;
        }
        if ($data['respCode'] != 'success') {
            return false;
        }
        if (empty($data['bizData'])) {
            return false;
        }
        $data = json_decode($data['bizData'], true);
        if (!isset($data['resultStatus']) || $data['resultStatus'] != 'success') {
            return false;
        }
        try {
            // @todo 更新订单状态，支付完成
            $info = [
                'pay_status' => 4,
                'pay_no' => $data['outOrderNo'],
                'money' => $data['amount'],
                'trade_no' => $data['sandSerialNo'],
                'receipt_data' => '',
            ];
            OrderError::getInstance()->insert(['text' => "进来杉德回调了：" . json_encode($info)]);
            $event["exchange"] = config('rabbitmq.order_callback_queue');
            RabbitMqService::send($event, $info);
        } catch (\Exception $e) {
            OrderError::getInstance()->insert(['text' => '杉德回调异常:' . $e->getMessage()]);
            return false;
        }
        return true;
    }

    /**
     * 生成AESKey
     * @param $size
     * @return string
     */
    private function aes_generate($size): string
    {
        $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        $arr = array();
        for ($i = 0; $i < $size; $i++) {
            $arr[] = $str[mt_rand(0, 61)];
        }

        return implode('', $arr);
    }

    /**
     * AES加密
     * @param $plainText
     * @param $key
     * @return string
     * @throws Exception
     */
    private function AESEncrypt($plainText, $key): string
    {
        ksort($plainText);
        $plainText = json_encode($plainText, JSON_UNESCAPED_UNICODE);
        $ivLen = openssl_cipher_iv_length($cipher = "AES-128-ECB");
        $iv = !$ivLen ? "" : openssl_random_pseudo_bytes($ivLen);
        $result = openssl_encrypt($plainText, 'AES-128-ECB', $key, OPENSSL_RAW_DATA, $iv);
        if (!$result) {
            throw new Exception('报文加密错误');
        }
        return base64_encode($result);
    }

    /**
     * AES解密
     * @param $cipherText
     * @param $key
     * @return string
     * @throws Exception
     */
    private function AESDecrypt($cipherText, $key): string
    {
        $result = openssl_decrypt(base64_decode($cipherText), 'AES-128-ECB', $key, 1);

        if (!$result) {
            throw new Exception('报文解密错误', 2003);
        }

        return $result;
    }

    private function RSAEncryptByPub($plainText, $puk): string
    {
        if (!openssl_public_encrypt($plainText, $cipherText, $puk, OPENSSL_PKCS1_PADDING)) {
            throw new Exception('AESKey 加密错误');
        }

        return base64_encode($cipherText);
    }

    /**
     * 公钥验签
     * @param $plainText
     * @param $sign
     * @param $path
     * @return int
     * @throws Exception
     */
    private function verify($plainText, $sign, $path): int
    {
        $resource = openssl_pkey_get_public($path);
        $result = openssl_verify($plainText, base64_decode($sign), $resource, 'SHA256');
        //在PHP8.0以后，即时编译（JIT）模式, 性能已经提高了90%, 你不再需要手动释放openssl_free_key()资源。
        //PHP的垃圾回收机制会自动处理这些内存管理问题。所以，你不需要担心内存占用的问题。
        openssl_free_key($resource);

        if (!$result) {
            throw new Exception('签名验证未通过,plainText:' . $plainText . '。sign:' . $sign, '02002');
        }

        return $result;
    }

    /**
     * 私钥解密AESKey
     * @param $cipherText
     * @param $prk
     * @return string
     * @throws Exception
     */
    private function RSADecryptByPri($cipherText, $prk): string
    {
        if (!openssl_private_decrypt(base64_decode($cipherText), $plainText, $prk, OPENSSL_PKCS1_PADDING)) {
            throw new Exception('AESKey 解密错误');
        }

        return (string)$plainText;
    }


    /**
     * 获取公钥
     * @param $path
     * @return mixed
     * @throws Exception
     */
    private function loadX509Cert($path)
    {
        try {
            $file = file_get_contents($path);
            if (!$file) {
                throw new Exception('loadx509Cert::file_get_contents ERROR');
            }
            $cert = chunk_split(base64_encode($file), 64, "\n");
            $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";
            $res = openssl_pkey_get_public($cert);
            $detail = openssl_pkey_get_details($res);
            openssl_free_key($res);

            if (!$detail) {
                throw new Exception('loadX509Cert::openssl_pkey_get_details ERROR');
            }

            return $detail['key'];
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 获取私钥
     * @param $path
     * @param $pwd
     * @return mixed
     * @throws Exception
     */
    function loadPk12Cert($path, $pwd)
    {
        try {
            $file = file_get_contents($path);
            if (!$file) {
                throw new Exception('loadPk12Cert::file
                        _get_contents');
            }

            if (!openssl_pkcs12_read($file, $cert, $pwd)) {
                throw new Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
            }
            return $cert['pkey'];
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 私钥签名
     * @param $plainText
     * @param $path
     * @return string
     * @throws Exception
     */
    public function sign($plainText, $path): string
    {
        //$plainText = json_encode($plainText);
        try {
            $resource = openssl_pkey_get_private($path);
            $result = openssl_sign($plainText, $sign, $resource, OPENSSL_ALGO_SHA256);
            //在PHP8.0以后，即时编译（JIT）模式, 性能已经提高了90%, 你不再需要手动释放openssl_free_key()资源。
            //PHP的垃圾回收机制会自动处理这些内存管理问题。所以，你不需要担心内存占用的问题。
            openssl_free_key($resource);

            if (!$result) {
                throw new Exception('签名出错' . $plainText);
            }

            return base64_encode($sign);
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 发送请求
     * @param $url
     * @param $param
     * @return bool|mixed
     * @throws Exception
     */
    private function http_post_json($url, $param)
    {
        if (empty($url) || empty($param)) {
            return false;
        }
        //NONE模式
        if ($param['encryptType'] == 'NONE') {
            $param['bizData'] = json_decode($param['bizData']);  //字符串转对象
        }
        $param = json_encode($param, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        try {
            $ch = curl_init();//初始化curl
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            //正式环境时解开注释
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            $data = curl_exec($ch);//运行curl
            curl_close($ch);

            if (!$data) {
                throw new Exception('请求出错');
            }
            return $data;
        } catch (Exception $e) {
            throw $e;
        }

    }
}